/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.extend.provider;

import cn.hutool.core.util.StrUtil;
import cn.star.framework.extend.config.Cfg;
import cn.star.framework.extend.config.AvailableSettings;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.service.UnknownUnwrapTypeException;

/**
 * 基于Hikari连接池的实现<br>
 * 参考：{@link com.zaxxer.hikari.hibernate.HikariConnectionProvider} 实现<br>
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
public class HikariConnectionProvider extends ConnectionProvider {

  private HikariDataSource dataSource;

  public HikariConnectionProvider() {
    log.info("{} init", HikariConnectionProvider.class.getName());
  }

  @Override
  DataSource getDataSource() {
    return dataSource;
  }

  @Override
  public boolean isUnwrappableAs(Class unwrapType) {
    return org.hibernate.engine.jdbc.connections.spi.ConnectionProvider.class.equals(unwrapType)
        || HikariConnectionProvider.class.isAssignableFrom(unwrapType);
  }

  @Override
  @SuppressWarnings("unchecked")
  public <T> T unwrap(Class<T> unwrapType) {
    if (org.hibernate.engine.jdbc.connections.spi.ConnectionProvider.class.equals(unwrapType)
        || HikariConnectionProvider.class.isAssignableFrom(unwrapType)) {
      return (T) this;
    } else if (DataSource.class.isAssignableFrom(unwrapType)) {
      return (T) this.dataSource;
    } else {
      throw new UnknownUnwrapTypeException(unwrapType);
    }
  }

  @Override
  public void stop() {
    this.dataSource.close();
  }

  @Override
  public void configure(Map configurationValues) {
    Cfg cfg = (Cfg) configurationValues.get(AvailableSettings.DYNAMIC_CFG);
    if (cfg.getDataSource() != null && cfg.getDataSource() instanceof HikariDataSource) {
      this.dataSource = (HikariDataSource) cfg.getDataSource();
    } else {
      // 创建 HikariCP pool
      HikariConfig config = this.builder(cfg);

      // 创建 HikariCP ds
      this.dataSource = new HikariDataSource(config);
    }
  }

  private HikariConfig builder(Cfg cfg) {
    HikariConfig config = new HikariConfig();

    this.setJdbcUrl(config, cfg.getUrl());
    this.setUsername(config, cfg.getUsername());
    this.setPassword(config, cfg.getPassword());
    this.setDriverClassName(config, cfg.getDriver());
    this.setMaximumPoolSize(config, cfg.getPoolSize());

    String sql = cfg.getTestQuery();
    if (StrUtil.isNotBlank(sql)) {
      cfg.addProperty(AvailableSettings.HIKARI_CONNECTION_TEST_QUERY, sql);
    }

    this.setConnectionTestQuery(
        config, cfg.getProperty(AvailableSettings.HIKARI_CONNECTION_TEST_QUERY));
    this.setConnectionInitSql(
        config, cfg.getProperty(AvailableSettings.HIKARI_CONNECTION_INIT_SQL));
    this.setReadOnly(config, cfg.getProperty(AvailableSettings.HIKARI_READ_ONLY));
    this.setValidationTimeout(config, cfg.getProperty(AvailableSettings.HIKARI_VALIDATION_TIMEOUT));
    this.setAutoCommit(config, cfg.getProperty(AvailableSettings.HIKARI_AUTO_COMMIT));
    this.setLeakDetectionThreshold(
        config, cfg.getProperty(AvailableSettings.HIKARI_LEAK_DETECTION_THRESHOLD));
    this.setConnectionTimeout(config, cfg.getProperty(AvailableSettings.HIKARI_CONNECTION_TIMEOUT));
    this.setIdleTimeout(config, cfg.getProperty(AvailableSettings.HIKARI_IDLE_TIMEOUT));
    this.setMinimumIdle(config, cfg.getProperty(AvailableSettings.HIKARI_MINIMUM_IDLE));
    this.setCatalog(config, cfg.getProperty(AvailableSettings.HIKARI_CATALOG));
    this.setMaxLifetime(config, cfg.getProperty(AvailableSettings.HIKARI_MAX_LIFETIME));
    this.setSchema(config, cfg.getProperty(AvailableSettings.HIKARI_SCHEMA));
    this.setPoolName(config, cfg.getProperty(AvailableSettings.HIKARI_POOL_NAME));
    this.setKeepaliveTime(config, cfg.getProperty(AvailableSettings.HIKARI_KEEPALIVE_TIME));
    this.setInitializationFailTimeout(
        config, cfg.getProperty(AvailableSettings.HIKARI_INITIALIZATION_FAIL_TIMEOUT));
    this.setDataSourceClassName(
        config, cfg.getProperty(AvailableSettings.HIKARI_DATA_SOURCE_CLASS_NAME));
    this.setRegisterMbeans(config, cfg.getProperty(AvailableSettings.HIKARI_REGISTER_MBEANS));
    this.setExceptionOverrideClassName(
        config, cfg.getProperty(AvailableSettings.HIKARI_EXCEPTION_OVERRIDE_CLASS_NAME));
    this.setHealthCheckRegistry(
        config, cfg.getProperty(AvailableSettings.HIKARI_HEALTH_CHECK_REGISTRY));
    this.setMetricRegistry(config, cfg.getProperty(AvailableSettings.HIKARI_METRIC_REGISTRY));
    this.setIsolateInternalQueries(
        config, cfg.getProperty(AvailableSettings.HIKARI_ISOLATE_INTERNAL_QUERIES));
    this.setTransactionIsolation(
        config, cfg.getProperty(AvailableSettings.HIKARI_TRANSACTION_ISOLATION));
    this.setAllowPoolSuspension(
        config, cfg.getProperty(AvailableSettings.HIKARI_ALLOW_POOL_SUSPENSION));
    this.setDataSourceJndi(config, cfg.getProperty(AvailableSettings.HIKARI_DATA_SOURCE_JNDI));

    // Class instance

    this.setHealthCheckProperties(
        config, cfg.getProperty(AvailableSettings.HIKARI_HEALTH_CHECK_PROPERTIES));
    this.setMetricsTrackerFactory(
        config, cfg.getProperty(AvailableSettings.HIKARI_METRICS_TRACKER_FACTORY));
    this.setThreadFactory(config, cfg.getProperty(AvailableSettings.HIKARI_THREAD_FACTORY));
    this.setDataSource(config, cfg.getProperty(AvailableSettings.HIKARI_DATA_SOURCE));
    this.setScheduledExecutor(config, cfg.getProperty(AvailableSettings.HIKARI_SCHEDULED_EXECUTOR));
    this.setDataSourceProperties(
        config, cfg.getProperty(AvailableSettings.HIKARI_DATA_SOURCE_PROPERTIES));

    return config;
  }

  private void setReadOnly(HikariConfig config, Object value) {
    try {
      config.setReadOnly((boolean) value);
    } catch (Exception ignore) {
    }
  }

  private void setCatalog(HikariConfig config, Object value) {
    try {
      config.setCatalog((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setMaxLifetime(HikariConfig config, Object value) {
    try {
      config.setMaxLifetime((long) value);
    } catch (Exception ignore) {
    }
  }

  private void setIdleTimeout(HikariConfig config, Object value) {
    try {
      config.setIdleTimeout((long) value);
    } catch (Exception ignore) {
    }
  }

  private void setMinimumIdle(HikariConfig config, Object value) {
    try {
      config.setMinimumIdle((int) value);
    } catch (Exception ignore) {
    }
  }

  private void setPassword(HikariConfig config, Object value) {
    try {
      config.setPassword((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setUsername(HikariConfig config, Object value) {
    try {
      config.setUsername((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setConnectionTimeout(HikariConfig config, Object value) {
    try {
      config.setConnectionTimeout((long) value);
    } catch (Exception ignore) {
    }
  }

  private void setMaximumPoolSize(HikariConfig config, Object value) {
    try {
      config.setMaximumPoolSize((int) value);
    } catch (Exception ignore) {
    }
  }

  private void setLeakDetectionThreshold(HikariConfig config, Object value) {
    try {
      config.setLeakDetectionThreshold((long) value);
    } catch (Exception ignore) {
    }
  }

  private void setValidationTimeout(HikariConfig config, Object value) {
    try {
      config.setValidationTimeout((long) value);
    } catch (Exception ignore) {
    }
  }

  private void setConnectionTestQuery(HikariConfig config, Object value) {
    try {
      config.setConnectionTestQuery((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setConnectionInitSql(HikariConfig config, Object value) {
    try {
      config.setConnectionInitSql((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setDataSourceProperties(HikariConfig config, Object value) {
    try {
      config.setDataSourceProperties((Properties) value);
    } catch (Exception ignore) {
    }
  }

  private void setInitializationFailTimeout(HikariConfig config, Object value) {
    try {
      config.setInitializationFailTimeout((long) value);
    } catch (Exception ignore) {
    }
  }

  private void setRegisterMbeans(HikariConfig config, Object value) {
    try {
      config.setRegisterMbeans((boolean) value);
    } catch (Exception ignore) {
    }
  }

  private void setMetricRegistry(HikariConfig config, Object value) {
    try {
      config.setMetricRegistry(value);
    } catch (Exception ignore) {
    }
  }

  private void setAllowPoolSuspension(HikariConfig config, Object value) {
    try {
      config.setAllowPoolSuspension((boolean) value);
    } catch (Exception ignore) {
    }
  }

  private void setIsolateInternalQueries(HikariConfig config, Object value) {
    try {
      config.setIsolateInternalQueries((boolean) value);
    } catch (Exception ignore) {
    }
  }

  private void setExceptionOverrideClassName(HikariConfig config, Object value) {
    try {
      config.setExceptionOverrideClassName((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setDriverClassName(HikariConfig config, Object value) {
    try {
      config.setDriverClassName((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setDataSourceClassName(HikariConfig config, Object value) {
    try {
      config.setDataSourceClassName((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setScheduledExecutor(HikariConfig config, Object value) {
    try {
      config.setScheduledExecutor((ScheduledExecutorService) value);
    } catch (Exception ignore) {
    }
  }

  private void setMetricsTrackerFactory(HikariConfig config, Object value) {
    try {
      config.setMetricsTrackerFactory((MetricsTrackerFactory) value);
    } catch (Exception ignore) {
    }
  }

  private void setHealthCheckRegistry(HikariConfig config, Object value) {
    try {
      config.setHealthCheckRegistry(value);
    } catch (Exception ignore) {
    }
  }

  private void setDataSourceJndi(HikariConfig config, Object value) {
    try {
      config.setDataSourceJNDI((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setTransactionIsolation(HikariConfig config, Object value) {
    try {
      config.setTransactionIsolation((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setHealthCheckProperties(HikariConfig config, Object value) {
    try {
      config.setHealthCheckProperties((Properties) value);
    } catch (Exception ignore) {
    }
  }

  private void setDataSource(HikariConfig config, Object value) {
    try {
      config.setDataSource((DataSource) value);
    } catch (Exception ignore) {
    }
  }

  private void setKeepaliveTime(HikariConfig config, Object value) {
    try {
      config.setKeepaliveTime((long) value);
    } catch (Exception ignore) {
    }
  }

  private void setPoolName(HikariConfig config, Object value) {
    try {
      config.setPoolName((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setThreadFactory(HikariConfig config, Object value) {
    try {
      config.setThreadFactory((ThreadFactory) value);
    } catch (Exception ignore) {
    }
  }

  private void setJdbcUrl(HikariConfig config, Object value) {
    try {
      config.setJdbcUrl((String) value);
    } catch (Exception ignore) {
    }
  }

  private void setAutoCommit(HikariConfig config, Object value) {
    try {
      config.setAutoCommit((boolean) value);
    } catch (Exception ignore) {
    }
  }

  private void setSchema(HikariConfig config, Object value) {
    try {
      config.setSchema((String) value);
    } catch (Exception ignore) {
    }
  }
}
